package btree;

import java.awt.HeadlessException;
import java.io.IOException;
import java.util.Stack;

import bufmgr.BufMgrException;
import bufmgr.BufferPoolExceededException;
import bufmgr.HashEntryNotFoundException;
import bufmgr.HashOperationException;
import bufmgr.InvalidFrameNumberException;
import bufmgr.PageNotReadException;
import bufmgr.PagePinnedException;
import bufmgr.PageUnpinnedException;
import bufmgr.ReplacerException;

import diskmgr.DB;
import diskmgr.DiskMgrException;
import diskmgr.DuplicateEntryException;
import diskmgr.FileEntryNotFoundException;
import diskmgr.FileIOException;
import diskmgr.FileNameTooLongException;
import diskmgr.InvalidPageNumberException;
import diskmgr.InvalidRunSizeException;
import diskmgr.OutOfSpaceException;
import diskmgr.Page;
import global.AttrType;
import global.Convert;
import global.PageId;
import global.RID;
import global.SystemDefs;
import heap.HFPage;
import heap.InvalidSlotNumberException;


public class BTreeFile extends IndexFile {
	private PageId root;
	private BTIndexPage rootPage;
	private int keyType;
	private Stack<PageId> stack = new Stack<PageId>();
	private String filename;
	private int heigh = 1;
	private static int maxKeySize = 0;
	private PageId tempRoot;
	public BTreeFile(String filename) throws FileIOException,
			InvalidPageNumberException, DiskMgrException, IOException,
			ReplacerException, HashOperationException, PageUnpinnedException,
			InvalidFrameNumberException, PageNotReadException,
			BufferPoolExceededException, PagePinnedException, BufMgrException,
			ConstructPageException, InsertRecException,
			HashEntryNotFoundException {
		this.filename = filename;
		tempRoot = SystemDefs.JavabaseDB.get_file_entry(filename);
		HFPage hp=new HFPage();
		SystemDefs.JavabaseBM.pinPage(tempRoot, hp, false);
		keyType = Convert.getIntValue(0, hp.getpage());
		root=hp.getNextPage();
		rootPage=new BTIndexPage(root, keyType);
		
		if (root == null)
			throw new IOException();
	}

	public BTreeFile(String string, int keyType, int i, int j)
			throws FileIOException, InvalidPageNumberException,
			DiskMgrException, IOException, Exception {
		this.keyType = keyType;
		
		this.filename=string;
		tempRoot = SystemDefs.JavabaseDB.get_file_entry(string);
		if (tempRoot == null) {
			// TODO there are other two constructors
			Page p = new Page();
			tempRoot=SystemDefs.JavabaseBM.newPage(p, 1);
			SystemDefs.JavabaseDB.add_file_entry(filename, tempRoot);
			HFPage h=new HFPage();
			h.init(tempRoot, p);
			tempRoot=h.getCurPage();
			Convert.setIntValue(keyType, 0, h.getpage());
			rootPage = new BTIndexPage(keyType);
			h.setNextPage(rootPage.getCurPage());
			root = rootPage.getCurPage();
			rootPage.Init();
			
			
			
		} 
//			else
//			tempRoot = SystemDefs.JavabaseDB.get_file_entry(string);
	}

	@Override
	public void insert(KeyClass data, RID rid) throws ConstructPageException,
			BufferPoolExceededException, HashOperationException,
			ReplacerException, HashEntryNotFoundException,
			InvalidFrameNumberException, PagePinnedException,
			PageUnpinnedException, PageNotReadException, BufMgrException,
			DiskMgrException, InsertRecException, KeyNotMatchException,
			NodeNotMatchException, ConvertException, DeleteRecException,
			InvalidSlotNumberException, IOException {
		stack = new Stack<PageId>();
		insert(rootPage, data, rid, 1);
	}

	public void insert(BTIndexPage head, KeyClass data, RID rid, int currLevel)
			throws IOException, ConstructPageException,
			BufferPoolExceededException, HashOperationException,
			ReplacerException, HashEntryNotFoundException,
			InvalidFrameNumberException, PagePinnedException,
			PageUnpinnedException, PageNotReadException, BufMgrException,
			DiskMgrException, InsertRecException, KeyNotMatchException,
			NodeNotMatchException, ConvertException, DeleteRecException,
			InvalidSlotNumberException {
		stack.add(head.getCurPage());
		
		KeyDataEntry currentRecord = getTargetRecord(head, data);
		IndexData indexData = (IndexData) currentRecord.data;
		PageId paId = indexData.getData();

		if (currLevel == heigh) {
			BTLeafPage leafPage = new BTLeafPage(paId, keyType);
			
			RID record = leafPage.insertRecord(data, rid);

			if (record == null) {
				
				// split the leaf page
				int numberOfSlots = leafPage.getSlotCnt();
				int half = (leafPage.getSlotCnt() + 1) / 2;
				BTLeafPage first = new BTLeafPage(keyType);
			

				PageId firstPageID = new PageId();
				firstPageID = first.getCurPage();

				// here we made a new page and want to add half the leaf page on
			
				RID temp = new RID();

				currentRecord = leafPage.getFirst(temp);
				int halfTemp = half - 1;
				while (halfTemp != 0) {
					currentRecord = leafPage.getNext(temp);
					halfTemp--;
				}
				// because we will push up the meddle record
				KeyDataEntry upEntry = currentRecord;
				for (int i = half; i <= numberOfSlots; i++) {
					first.insertRecord(currentRecord.key, ((LeafData)currentRecord.data).getData());

					KeyDataEntry TempREcord = null;

					TempREcord = leafPage.getNext(new RID(temp.pageNo,
							temp.slotNo));
					leafPage.delEntry(currentRecord);
					if (TempREcord != null)
						currentRecord = TempREcord;
				}
				if (BT.keyCompare(data, upEntry.key) > 0)
					first.insertRecord(data, rid);
				else
					leafPage.insertRecord(data, rid);

				leafPage.setNextPage(firstPageID);
				pushUp(upEntry, head, firstPageID);
				// because i removed the new
				// in the last code we splite and push up we must insert the
				// record then
				
				System.out.print(head.getSlotCnt() + "  kkk");
				BT.printPage(first.getCurPage(), keyType);
			}

			SystemDefs.JavabaseBM.unpinPage(paId, true);
			BT.printPage(leafPage.getCurPage(), keyType);

		} else {
			BTIndexPage page = new BTIndexPage(paId, keyType);
			insert(page, data, rid, currLevel + 1);
			SystemDefs.JavabaseBM.unpinPage(paId, false);
		}

		// }

	}

	// TODO the root may be full and will be null
	private void pushUp(KeyDataEntry key, BTIndexPage parentPage, PageId pageID)
			throws InsertRecException, IOException,
			BufferPoolExceededException, HashOperationException,
			ReplacerException, HashEntryNotFoundException,
			InvalidFrameNumberException, PagePinnedException,
			PageUnpinnedException, PageNotReadException, BufMgrException,
			DiskMgrException, KeyNotMatchException, NodeNotMatchException,
			ConvertException, InvalidSlotNumberException,
			ConstructPageException, DeleteRecException {

		RID rid = new RID();
		rid = parentPage.insertKey(key.key, pageID);
		if (rid == null) {

			int half = (parentPage.getSlotCnt() + 1) / 2;
			int numOfSlots = parentPage.getSlotCnt();
			BTIndexPage first = new BTIndexPage(keyType);

			PageId firstPageID = new PageId();
			firstPageID = first.getCurPage();
			RID temp = new RID();
			KeyDataEntry currentRecord = parentPage.getFirst(temp);

			int halfTemp = half - 1;
			while (halfTemp != 0) {

				currentRecord = parentPage.getNext(temp);
				halfTemp--;

			}
			KeyDataEntry upEntry = currentRecord;
			for (int i = half; i <= numOfSlots; i++) {

				if(i==half)
					first.setLeftLink(((IndexData) (currentRecord.data)).getData());
				else
					first.insertKey(currentRecord.key,
						((IndexData) (currentRecord.data)).getData());
				KeyDataEntry TempREcord = null;

				TempREcord = parentPage.getNext(new RID(temp.pageNo,
						temp.slotNo));
				parentPage.deleteSortedRecord(temp);
				if (TempREcord != null)
					currentRecord = TempREcord;
			}
			if(BT.keyCompare(key.key, upEntry.key)>0)
				first.insertKey(key.key, pageID);
			else
				parentPage.insertKey(key.key, pageID);
			try {
				stack.pop();
				PageId pageId = stack.pop();

				BTIndexPage page = new BTIndexPage(pageId, keyType);

				pushUp(upEntry, page, firstPageID);

				SystemDefs.JavabaseBM.unpinPage(pageId, false);
			} catch (Exception e) {
				BTIndexPage newRootPage = new BTIndexPage(keyType);
				PageId newRootId = newRootPage.getCurPage();
				newRootPage.setLeftLink(parentPage.getCurPage());
				newRootPage.insertKey(upEntry.key, first.getCurPage());
				root = newRootId;
				rootPage = newRootPage;
				heigh++;
				
			}
		}
	}

	@Override
	public boolean Delete(KeyClass data, RID rid) throws Exception {
		BTLeafPage targetLeafPage = GetleafPage(rootPage, data, 1);
		Boolean b = targetLeafPage.delEntry(new KeyDataEntry(data, rid));
		BT.printPage(targetLeafPage.getCurPage(), keyType);
		return b;
	}

	public BTLeafPage GetleafPage(BTIndexPage page, KeyClass data,
			int currentHeigh) throws Exception {

		if (currentHeigh == heigh) {
			KeyDataEntry entry = getTargetRecord((BTIndexPage) page, data);
			PageId id = ((IndexData) (entry.data)).getData();
			BTLeafPage page2 = new BTLeafPage(id, keyType);
			SystemDefs.JavabaseBM.unpinPage(page2.getCurPage(), true);
			return page2;
		} else {
			KeyDataEntry entry = getTargetRecord((BTIndexPage) page, data);
			PageId id = ((IndexData) (entry.data)).getData();
			BTIndexPage childPage = new BTIndexPage(id, keyType);
			SystemDefs.JavabaseBM.unpinPage(childPage.getCurPage(), true);
			BTLeafPage ret = GetleafPage(childPage, data, currentHeigh + 1);
			return ret;

		}
	}
//TODO
	public void close() throws ReplacerException, PageUnpinnedException,
			HashEntryNotFoundException, InvalidFrameNumberException, IOException, FileNameTooLongException, InvalidPageNumberException, InvalidRunSizeException, DuplicateEntryException, OutOfSpaceException, FileIOException, DiskMgrException, FileEntryNotFoundException {
		SystemDefs.JavabaseBM.unpinPage(root, false);

		SystemDefs.JavabaseDB.delete_file_entry(filename);
	}

	public void destroyFile() throws FileEntryNotFoundException,
			FileIOException, InvalidPageNumberException, DiskMgrException,
			IOException {
		SystemDefs.JavabaseDB.delete_file_entry(filename);
	}

	public BTFileScan new_scan(KeyClass lo_key, KeyClass hi_key)
			throws FileIOException, InvalidPageNumberException,
			DiskMgrException, ReplacerException, HashOperationException,
			PageUnpinnedException, InvalidFrameNumberException,
			PageNotReadException, BufferPoolExceededException,
			PagePinnedException, BufMgrException, IOException,
			KeyNotMatchException, NodeNotMatchException, ConvertException,
			HashEntryNotFoundException, ConstructPageException {
		BTFileScan.setHead(getHeaderPage());

		return new BTFileScan(lo_key, hi_key);

	}

	public BTIndexPage getHeaderPage() {

		return rootPage;
	}

	public void traceFilename(String string) {

	}

	public KeyDataEntry getTargetRecord(BTIndexPage inPage, KeyClass key)
			throws KeyNotMatchException, NodeNotMatchException,
			ConvertException, IOException {
		RID firstRid = new RID();
		KeyDataEntry first = new KeyDataEntry(Integer.MIN_VALUE,
				inPage.getLeftLink());
		KeyDataEntry second = inPage.getFirst(firstRid);
		while (second != null && BT.keyCompare(key, second.key) >= 0) {
			KeyDataEntry data = second;
			second = inPage.getNext(firstRid);
			first = data;
		}

		return first;

	}

	public static int getMaxSize() {
		return maxKeySize;
	}
}
